package utils

import (
	"bytes"
	"compress/gzip"
	"crypto/md5"
	crand "crypto/rand"
	"crypto/sha1"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"hash/crc32"
	"io"
	"io/ioutil"
	"math"
	"math/rand"
	"net/url"
	"os"
	"path/filepath"
	"reflect"
	"regexp"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/chenhg5/collection"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
)

// IsNil 判断interface是否为空
func IsNil(i interface{}) bool {
	vi := reflect.ValueOf(i)
	if vi.Kind() == reflect.Ptr {
		return vi.IsNil()
	}
	return i == nil
}

func Trim(s string) string {
	return strings.TrimSpace(s)
}

// Tables 服务列表
var Tables = make(map[string]interface{})

// D 获取数据库对象
func D(tablename string) interface{} {
	return Tables[tablename]
}

// MongoID 将字符串形式的mongoid转为ObjectId
func MongoID(id interface{}) (primitive.ObjectID, error) {
	if d, ok := id.(string); ok {
		if primitive.IsValidObjectID(d) {
			d1, err := primitive.ObjectIDFromHex(d)
			return d1, err
		}
	} else if d, ok := id.(primitive.ObjectID); ok {
		return d, nil
	}
	return primitive.ObjectID{}, errors.New("参数错误！")
}

func IsMongoID(id string) bool {
	b, _ := IsMatch("^[A-Fa-f0-9]{24}$", id)
	return b
}

// GetMongoID 将ObjectId转为字符串
func GetMongoID(id primitive.ObjectID) string {
	return id.Hex()
}

func MongoRegex(pattern string, mode string) primitive.Regex {
	if "" == mode {
		mode = "i"
	}
	return primitive.Regex{
		Pattern: pattern,
		Options: mode,
	}
}

func Rand(min int, max int) int {
	return min + rand.Intn(max-min)
}

// Md5 获取字符串的md5编码
func Md5(str string) string {
	h := md5.New()
	h.Write([]byte(str))
	return hex.EncodeToString(h.Sum(nil))
}

func CheckMd5(str string) bool {
	b, _ := IsMatch("^[A-Fa-f0-9]{32}$", str)
	return b
}

// CheckUsername 检查用户名是否合法
func CheckUsername(name string) (bool, error) {
	return IsMatch("^[a-z0-9_-]{3,16}$", name)
}

// CheckPassword 检查密码是否合法
func CheckPassword(value string) int {
	var level = 0
	if len(value) >= 6 {
		if b, _ := IsMatch("\\d", value); b {
			level++
		}
		if b, _ := IsMatch("[a-z]", value); b {
			level++
		}
		if b, _ := IsMatch("[A-Z]", value); b {
			level++
		}
		if b, _ := IsMatch("\\W", value); b {
			level++
		}
	}
	return level
}

func CheckPhone(phone string) (bool, error) {
	return IsMatch("^[1]([3-9])[0-9]{9}$", phone)
}

// CheckIP 检查字符串是否符合IP规则
func CheckIP(ip string) (bool, error) {
	return IsMatch("^(\\d{1,2}|1\\d\\d|2[0-4]\\d|25[0-5])\\.(\\d{1,2}|1\\d\\d|2[0-4]\\d|25[0-5])\\.(\\d{1,2}|1\\d\\d|2[0-4]\\d|25[0-5])\\.(\\d{1,2}|1\\d\\d|2[0-4]\\d|25[0-5])$", ip)
}

func CheckMail(mail string) (bool, error) {
	return IsMatch(`^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$`, mail)
}

// IsMatch 检查正则是否匹配
func IsMatch(pattern string, str string) (bool, error) {
	return regexp.MatchString(pattern, str)
}

func Match(pattern, str string) string {
	ret, _ := MatchIndex(pattern, str, 0, 0)
	return ret
}

func MatchIndex(pattern string, str string, index, group int) (string, error) {
	pat, err := regexp.Compile(pattern)
	if err != nil {
		return "", err
	}
	ret := pat.FindAllStringSubmatch(str, -1)
	if len(ret) > 0 && len(ret) <= index+1 && len(ret[index]) <= group+1 {
		return ret[index][group], nil
	}
	return "", errors.New("匹配不到")
}

func Split(orig, sep string) []string {
	return strings.Split(orig, sep)
}

// Copy 复制对象
func Copy(data interface{}) interface{} {

	if _, ok := data.(bson.M); ok {
		t1 := &data
		t2 := *t1
		t3 := &t2
		return *t3
	}
	return nil
}

func EndWith(s, e string) bool {
	return strings.HasSuffix(s, e)
}

func BeginWith(s, b string) bool {
	return strings.HasPrefix(s, b)
}

// DeepCopy 深度复制一个map或者切片
func DeepCopy(value interface{}) interface{} {
	if valueMap, ok := value.(map[string]interface{}); ok {
		newMap := make(map[string]interface{})
		for k, v := range valueMap {
			newMap[k] = DeepCopy(v)
		}

		return newMap
	} else if valueSlice, ok := value.([]interface{}); ok {
		newSlice := make([]interface{}, len(valueSlice))
		for k, v := range valueSlice {
			newSlice[k] = DeepCopy(v)
		}

		return newSlice
	}

	return value
}

// Contains 查找是否包含
func Contains(arr interface{}, value interface{}) bool {
	return collection.Collect(arr).Contains(value)
}

// MapStringToMapInterface 将map转为interface元素
func MapStringToMapInterface(arr map[string]string) map[string]interface{} {
	ret := make(map[string]interface{})
	for k, v := range arr {
		ret[k] = v
	}
	return ret
}

func CombineURLs(base, u string) string {
	u, _ = url.JoinPath(base, u)
	return u
}

// GetFile 通过路径获取文件
func GetFile(path interface{}) (*os.File, error) {
	if filepath, ok := path.(string); ok && "" != filepath {
		return os.OpenFile(filepath, os.O_CREATE|os.O_RDWR, os.ModePerm)
	}
	return nil, fmt.Errorf("param path is nil string")
}

// IsDir 判断是否是文件目录
func IsDir(dirname string) bool {
	fhandler, err := os.Stat(dirname)
	if !(err == nil || os.IsExist(err)) {
		return false
	}
	return fhandler.IsDir()
}

// IsFile 判断是否是文件
func IsFile(filename string) bool {
	fhandler, err := os.Stat(filename)
	if !(err == nil || os.IsExist(err)) {
		return false
	} else if fhandler.IsDir() {
		return false
	}
	return true
}

// IsExist 判断路径是否存在
func IsExist(path string) (bool, error) {
	_, err := os.Stat(path)

	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return false, err
}

// GetFileByteSize 获取文件大小
func GetFileByteSize(filename string) (bool, int64) {
	if !IsFile(filename) {
		return false, 0
	}
	fhandler, _ := os.Stat(filename)
	return true, fhandler.Size()
}

// Write 写字符串进File对象
func Write(file *os.File, data string) (bool, error) {
	_, err := file.WriteString(data)
	if err != nil {
		return false, err
	}
	return true, nil
}

// Read 读取文件所有内容并转为字符串返回
func Read(path string) ([]byte, error) {
	file, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer file.Close()
	info, err := file.Stat()
	if nil != err {
		return nil, err
	}
	buf := make([]byte, info.Size())
	file.Read(buf)
	return buf, nil
}

func GetFileListByPath(dir string, isDeep bool) ([]string, error) {
	if !IsDir(dir) {
		return nil, fmt.Errorf("%s is not a dir", dir)
	}
	var ret []string
	entris, err := ioutil.ReadDir(dir)
	if err != nil {
		return nil, fmt.Errorf("ReadDir %s error : %s", dir, err)
	}
	for _, entry := range entris {
		if entry.IsDir() {
			if isDeep {
				l, err := GetFileListByPath(filepath.Join(dir, entry.Name()), isDeep)
				if err != nil {
					continue
				}
				ret = append(ret, l...)
			}
		} else {
			ret = append(ret, filepath.Join(dir, entry.Name()))
		}
	}
	return ret, nil
}

// GetDirFilesCount 获取目录文件数量和总大小
func GetDirFilesCount(dir string, isDeep bool) (count int, allSize int64, err error) {
	if !IsDir(dir) {
		return 0, 0, fmt.Errorf("%s is not a dir", dir)
	}
	var wg sync.WaitGroup
	wg.Add(1)
	var walk func(d string, size chan<- int64)
	walk = func(d string, size chan<- int64) {
		defer wg.Done()

		entries, err := ioutil.ReadDir(d)

		if err != nil {
			fmt.Println("GetDirFilesCount error : " + err.Error())
			return
		}

		for _, entry := range entries {
			if isDeep && entry.IsDir() {
				wg.Add(1)
				subDir := filepath.Join(d, entry.Name())
				go walk(subDir, size)
			} else {
				size <- entry.Size()
			}
		}

	}
	fileSizes := make(chan int64)

	go func() {
		wg.Wait()
		close(fileSizes)
	}()

	go walk(dir, fileSizes)

	allSize = 0
	for size := range fileSizes {
		count++
		allSize += size
	}
	return count, allSize, nil
}

// JsonToMap Convert json string to map
func JsonToMap(jsonStr string) (map[string]interface{}, error) {
	m := make(map[string]interface{})
	err := json.Unmarshal([]byte(jsonStr), &m)
	if err != nil {
		return nil, fmt.Errorf("JsonToMap Unmarshal with error: %+v", err)
	}

	return m, nil
}

func JsonToStruct(str string, out interface{}) error {
	err := json.Unmarshal([]byte(str), out)
	if err != nil {
		return fmt.Errorf("JsonToArray Unmarshal with error: %+v", err)
	}
	return nil
}

// MapToJson Convert map json string
func MapToJson(m map[string]interface{}) (string, error) {
	return ToJson(m)
}

func ToByte(data interface{}) ([]byte, error) {
	return json.Marshal(data)
}

func ToJson(data interface{}) (string, error) {
	jsonByte, err := ToByte(data)
	return string(jsonByte), err
}

func ToStruct(d []byte, ret interface{}) error {
	err := json.Unmarshal(d, ret)
	if err != nil {
		return fmt.Errorf("ToStruct Unmarshal error : %s", err)
	}
	return nil
}

func ToMap(data interface{}) (map[string]interface{}, error) {
	jsonData, err := ToByte(data)
	if err != nil {
		return nil, err
	}
	ret := map[string]interface{}{}
	err = ToStruct(jsonData, &ret)
	if err != nil {
		return nil, err
	}
	return ret, nil
}

func MapToStruct(m map[string]interface{}, ret interface{}) error {
	data, err := ToByte(m)
	if err != nil {
		return err
	}

	err = ToStruct(data, ret)
	return err
}

// Compare 比较两个对象
func Compare(a1, a2 interface{}) bool {
	return reflect.DeepEqual(a1, a2)
}

// Contain 判断obj是否在target中，target支持的类型arrary,slice,map
func Contain(obj interface{}, target interface{}) (bool, error) {
	targetValue := reflect.ValueOf(target)
	switch reflect.TypeOf(target).Kind() {
	case reflect.Slice, reflect.Array:
		for i := 0; i < targetValue.Len(); i++ {
			if targetValue.Index(i).Interface() == obj {
				return true, nil
			}
		}
	case reflect.Map:
		if targetValue.MapIndex(reflect.ValueOf(obj)).IsValid() {
			return true, nil
		}
	}

	return false, errors.New("not in array")
}

// MD5 生成md5
func MD5(str string) string {
	c := md5.New()
	c.Write([]byte(str))
	return hex.EncodeToString(c.Sum(nil))
}

func MD5Bit16(str string) string {
	return MD5(str)[8:24]
}

// SHA1 生成sha1
func SHA1(str string) string {
	c := sha1.New()
	c.Write([]byte(str))
	return hex.EncodeToString(c.Sum(nil))
}

// CRC32 生成crc32
func CRC32(str string) uint32 {
	return crc32.ChecksumIEEE([]byte(str))
}

// ToTime 将时间戳转为Time对象
func ToTime(tMillisecond int64) time.Time {
	return time.Unix(0, tMillisecond*int64(time.Millisecond))
}

// SecToTime 将秒单位时间戳转为Time对象
func SecToTime(tSecond int64) time.Time {
	return time.Unix(0, tSecond*int64(time.Second))
}

func IndexOfStr(data []string, str string) int {
	for idx, d := range data {
		if d == str {
			return idx
		}
	}
	return -1
}

func IndexOfMongid(data []primitive.ObjectID, id primitive.ObjectID) int {
	for idx, d := range data {
		if d.Hex() == id.Hex() {
			return idx
		}
	}
	return -1
}

func GenUUID() (string, error) {
	uuid := make([]byte, 16)
	n, err := io.ReadFull(crand.Reader, uuid)
	if n != len(uuid) || err != nil {
		return "", err
	}
	// variant bits; see section 4.1.1
	uuid[8] = uuid[8]&^0xc0 | 0x80
	// version 4 (pseudo-random); see section 4.1.3
	uuid[6] = uuid[6]&^0xf0 | 0x40
	return fmt.Sprintf("%x-%x-%x-%x-%x", uuid[0:4], uuid[4:6], uuid[6:8], uuid[8:10], uuid[10:]), nil
}

// func Round(f float64) int {
// 	return int(math.Floor(f + 0.5))
// }

func Round(f float64, precision int) float64 {
	p := math.Pow10(precision)
	return math.Trunc((f+0.5/p)*p) / p
}

func ToString(v interface{}) string {
	if nil != v {
		if ret, ok := v.(string); ok {
			return ret
		}
	}
	return ""
}

func ToFloat64(v interface{}) float64 {
	if v == nil {
		return 0.0
	}

	switch v.(type) {
	case float64:
		return v.(float64)
	case string:
		vStr := v.(string)
		vF, _ := strconv.ParseFloat(vStr, 64)
		return vF
	default:
		fmt.Printf("%v to float64 error.\n", v)
		return 0.0
	}
}

// StrToInt returns the parameter as a int64
func StrToInt(param string) (int64, error) {
	return strconv.ParseInt(param, 0, 64)
}

func ToInt(v interface{}) int {
	if v == nil {
		return 0
	}

	switch v.(type) {
	case string:
		vStr := v.(string)
		vInt, _ := strconv.Atoi(vStr)
		return vInt
	case int:
		return v.(int)
	case float64:
		vF := v.(float64)
		return int(vF)
	default:
		fmt.Printf("%v to int error\n", v)
		return 0
	}
}

func ToUint64(v interface{}) uint64 {
	if v == nil {
		return 0
	}

	switch v.(type) {
	case int:
		return uint64(v.(int))
	case float64:
		return uint64((v.(float64)))
	case string:
		uV, _ := strconv.ParseUint(v.(string), 10, 64)
		return uV
	default:
		fmt.Printf("%v to uint64 error.\n", v)
		return 0
	}
}

func ToInt64(v interface{}) int64 {
	if v == nil {
		return 0
	}

	switch v.(type) {
	case float64:
		return int64(v.(float64))
	default:
		vv := fmt.Sprint(v)

		if vv == "" {
			return 0
		}

		vvv, err := strconv.ParseInt(vv, 0, 64)
		if err != nil {
			return 0
		}

		return vvv
	}
}

func StringToInt(s string) (int, error) {
	return strconv.Atoi(s)
}

func IntToString(i int) string {
	return strconv.Itoa(i)
}

func FloatToString(v float64, precision int) string {
	return fmt.Sprint(FloatToFixed(v, precision))
}

func FloatToFixed(v float64, precision int) float64 {
	p := math.Pow(10, float64(precision))
	return math.Round(v*p) / p
}

func Join(strs []string, split string) string {
	ret := ""
	for _, str := range strs {
		if ret == "" {
			ret = str
		} else {
			ret = ret + split + str
		}
	}
	return ret
}

func IsString(value interface{}) bool {
	if nil == value {
		return false
	}
	switch value.(type) {
	case string:
		return true
	}
	return false
}

func GzipDecompress(data []byte) ([]byte, error) {
	r, err := gzip.NewReader(bytes.NewReader(data))
	if err != nil {
		return nil, err
	}
	return ioutil.ReadAll(r)
}

func ApiUrlEncode(data string) string {
	return url.QueryEscape(strings.Replace(data, "+", "/*+*/", -1))
}

func ApiUrlDecode(data string) (string, error) {
	data, err := url.QueryUnescape(data)
	if err != nil {
		return data, err
	}
	return strings.Replace(strings.Replace(data, "/*+*/", "+", -1), "/* */", "+", -1), nil
}

func MapToUrlencode(query map[string]string) string {
	var u = ""

	for i, data := range query {
		if u != "" {
			u += "&"
		}
		u += i + "=" + url.QueryEscape(data)
	}

	return u
}

func ZeroFill(num interface{}, len int) string {
	str := fmt.Sprintf("%v", num)
	return fmt.Sprintf("%0"+IntToString(len)+"s", str)
}

func ArrayToSlice(arr interface{}) interface{} {
	arrValue := reflect.ValueOf(arr)
	if arrValue.Kind() != reflect.Array {
		panic("Input is not an array")
	}
	sliceValue := reflect.MakeSlice(reflect.SliceOf(arrValue.Type().Elem()), arrValue.Len(), arrValue.Len())
	reflect.Copy(sliceValue, arrValue)
	return sliceValue.Interface()
}

func RemoveElement(arr interface{}, index, count int) interface{} {
	switch reflect.TypeOf(arr).Kind() {
	case reflect.Slice:
		s := reflect.ValueOf(arr)
		if s.Len() == 0 {
			return arr
		}
		if index >= s.Len() {
			return arr
		}
		if s.Len() == 1 {
			return reflect.MakeSlice(reflect.TypeOf(arr), 0, 0).Interface()
		}
		result := reflect.MakeSlice(reflect.TypeOf(arr), 0, s.Len()-count)
		result = reflect.AppendSlice(result, s.Slice(0, index))
		result = reflect.AppendSlice(result, s.Slice(index+count, s.Len()))
		return result.Interface()
	case reflect.Array:
		return RemoveElement(ArrayToSlice(arr), index, count)
	default:
		return arr
	}
}

// ParseJSONWithComment 解析含有注释的JSON文件
func ParseJSONWithComment(filepath string, v interface{}) error {
	// 读取文件内容
	content, err := ioutil.ReadFile(filepath)
	if err != nil {
		return err
	}
	// 去除注释
	j := ClearJSONComments(string(content))
	// 解析JSON
	return json.Unmarshal([]byte(j), v)
}

func ClearJSONComments(jsonStr string) string {
	// 匹配单行注释
	singleLineComment := regexp.MustCompile(`(?msU)\/\/.*$`)
	jsonStr = singleLineComment.ReplaceAllString(jsonStr, "")

	// 匹配多行注释
	multiLineComment := regexp.MustCompile(`(?msU)\/\*.*?\*\/`)
	jsonStr = multiLineComment.ReplaceAllString(jsonStr, "")

	Debug("jsonStr : %s", jsonStr)
	return jsonStr
}
